package org.krayne.swivel.container;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;

import org.krayne.swivel.util.ComponentUtil;

/**
 * A {@code TitleContainer} is a simple container that provides an easily
 * visible title. It can optionally be collapsible.
 * 
 * @author dhsu
 */
public class TitleContainer extends JPanel {
    private static final long serialVersionUID = -8513278300451544258L;
    
    // defaults
    private static final int DEFAULT_TITLE_PADDING = 3;
    private static final Color DEFAULT_TITLE_BACKGROUND_COLOR = Color.GRAY;
    private static final Color DEFAULT_TITLE_COLOR = Color.WHITE;
    
    // title
    private final JLabel titleLabel;
    private final JLabel ellipsisLabel;
    private final JPanel titlePanel;
    
    // main component
    private Component mainComponent;
    private final JPanel mainComponentPanelCollapsed;
    private final JPanel mainComponentPanel;
    
    // collapsing
    private final CollapsingMouseListener collapsingMouseListener;
    private boolean isCollapsed;

    /**
     * Constructs a {@code TitleContainer} that wraps the specified component.
     * 
     * @param component the main component
     */
    public TitleContainer(Component component) {
        this(component, "", false);
    }
    
    /**
     * Constructs a {@code TitleContainer} that wraps the specified component
     * and has the specified title.
     * 
     * @param component the main component
     * @param title the title
     */
    public TitleContainer(Component component, String title) {
        this(component, title, false);
    }
    
    /**
     * Constructs a {@code TitleContainer} that wraps the specified component,
     * has the specified title, and is collapsible as specified.
     * 
     * @param component the main component
     * @param title the title
     * @param collapsible whether the container is collapsible or not
     */
    public TitleContainer(Component component, String title, boolean collapsible) {
        super();
        this.titleLabel = new JLabel(title);
        this.ellipsisLabel = new JLabel(" ...");
        this.titlePanel = new JPanel(new BorderLayout());
        this.mainComponentPanelCollapsed = new JPanel(new BorderLayout());
        this.mainComponentPanel = new JPanel(new BorderLayout());
        this.collapsingMouseListener = new CollapsingMouseListener();
        
        // set collapse behavior
        setCollapsible(collapsible);

        // look and feel
        this.setTitleBackgroundColor(DEFAULT_TITLE_BACKGROUND_COLOR);
        this.setTitleColor(DEFAULT_TITLE_COLOR);
        this.setTitleBarPadding(DEFAULT_TITLE_PADDING);
        this.mainComponentPanelCollapsed.add(Box.createVerticalStrut(1), BorderLayout.CENTER);
        
        // layout
        this.titlePanel.add(titleLabel, BorderLayout.WEST);
        this.setLayout(new BorderLayout());
        this.add(titlePanel, BorderLayout.NORTH);
        this.add(mainComponentPanel, BorderLayout.CENTER);
        this.setComponent(component);
    }
    
    /**
     * Sets whether or not the container is collapsible. When the container is
     * collapsible, users can click the title bar to hide or show the
     * contained component.
     * 
     * @param collapsible whether or not the container should be collapsible
     */
    public void setCollapsible(boolean collapsible) {
        // remove the collapsing mouse listener
        this.titlePanel.removeMouseListener(collapsingMouseListener);
        
        // add the collapsing mouse listener if collapsible
        if(collapsible) {
            this.titlePanel.addMouseListener(collapsingMouseListener);
        }
    }
    
    /**
     * Gets whether or not the container is collapsible.
     * 
     * @return whether or not the container is collapsible
     */
    public boolean isCollapsible() {
        // since the title panel is private, we control all the mouse listeners.
        // if there is a mouse listener, then that means it is collapsible.
        int numMouseListeners = this.titlePanel.getMouseListeners().length;
        return (numMouseListeners > 0);
    }
    
    /**
     * This method provides a programmatic way to collapse the container.
     * 
     * @param collapsed whether the container should be collapsed or shown
     */
    public void setCollapsed(boolean collapsed) {
        if(isCollapsed == collapsed) {
            return;
        }
        
        if(collapsed) {
            this.titlePanel.add(ellipsisLabel, BorderLayout.CENTER);
            this.remove(mainComponentPanel);
            this.add(mainComponentPanelCollapsed, BorderLayout.CENTER);
        } else {
            this.titlePanel.remove(ellipsisLabel);
            this.remove(mainComponentPanelCollapsed);
            this.add(mainComponentPanel, BorderLayout.CENTER);
        }
        
        isCollapsed = collapsed;
        ComponentUtil.refreshComponent(this);
    }
    
    /**
     * Gets whether the container is collapsed or not.
     * 
     * @return whether the container is collapsed or not
     */
    public boolean isCollapsed() {
        return isCollapsed;
    }
    
    /**
     * Sets the title of this container.
     * 
     * @param title the title
     */
    public void setTitle(String title) {
        if(title != null) {
            this.titleLabel.setText(title);
        } else {
            this.titleLabel.setText("");
        }
    }
    
    /**
     * Gets the this container's title.
     * 
     * @return the title
     */
    public String getTitle() {
        return this.titleLabel.getText();
    }
    
    /**
     * Sets the main component for this container.
     * 
     * @param component the main component
     */
    public void setComponent(Component component) {
        // don't need to do anything if the main component hasn't changed
        if(component == this.mainComponent) {
            return;
        }
        
        // remove the main component
        if(this.mainComponent != null) {
            mainComponentPanel.remove(this.mainComponent);
        }

        // replace the main component
        this.mainComponent = component;
        mainComponentPanel.add(component, BorderLayout.CENTER);
        
        // repaint main component
        ComponentUtil.refreshComponent(this);
    }
    
    /**
     * Gets the main component for this container.
     * 
     * @return the main component
     */
    public Component getComponent() {
        return this.mainComponent;
    }
    
    /**
     * Sets the title font.
     * 
     * @param font the title font
     */
    public void setTitleFont(Font font) {
        super.setFont(font);
        if(titleLabel != null) {
            titleLabel.setFont(font);
        }
        if(ellipsisLabel != null) {
            ellipsisLabel.setFont(font);
        }
    }
    
    /**
     * Sets the background color of the title bar.
     * 
     * @param color the color
     */
    public void setTitleBackgroundColor(Color color) {
        this.titlePanel.setBackground(color);
    }
    
    /**
     * Sets the title text color.
     * 
     * @param color the color
     */
    public void setTitleColor(Color color) {
        this.titleLabel.setForeground(color);
        this.ellipsisLabel.setForeground(color);
    }
    
    /**
     * Sets the title bar padding in pixels.
     * 
     * @param padding the title bar padding in pixels
     */
    public void setTitleBarPadding(int padding) {
        Border border = BorderFactory.createEmptyBorder(padding, padding, padding, padding);
        this.titlePanel.setBorder(border);
    }
    
    /**
     * Sets the visibility of the title bar. If the title bar is invisible,
     * the user will not be able to collapse or decollapse the container.
     * 
     * @param visible visibility of the title bar
     */
    public void setTitleBarVisible(boolean visible) {
        this.titlePanel.setVisible(visible);
    }
    
    /**
     * Gets the visibility of the title bar.
     * 
     * @return true if the title bar is visible, false otherwise
     */
    public boolean isTitleBarVisible() {
        return this.titlePanel.isVisible();
    }
    
    //--------------------------------------------------------------------------
    
    /**
     * A {@code MouseListener} that changes the cursor when moved over the title
     * bar to indicate that it is clickable. Clicking the title bar collapses
     * the container.
     */
    private class CollapsingMouseListener extends MouseAdapter {
        private final Cursor CLICK_ME_CURSOR = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
        
        /**
         * Collapses or shows the titled component.
         * 
         * {@inheritDoc}
         */
        public void mousePressed(MouseEvent e) {
            setCollapsed(!TitleContainer.this.isCollapsed);
        }

        /**
         * Changes the cursor to indicate clickability.
         * 
         * {@inheritDoc}
         */
        public void mouseEntered(MouseEvent e) {
            titlePanel.setCursor(CLICK_ME_CURSOR);
        }

        /**
         * Changes the cursor back to the default.
         * 
         * {@inheritDoc}
         */
        public void mouseExited(MouseEvent e) {
            titlePanel.setCursor(Cursor.getDefaultCursor());
        }
    }
}
